/*
 * @Author chencheng
 * @Date 2024-01-16
 * Copyright (c) 2024 chencheng, Inc. <daniel_txy@163.com>
 */

package main

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"golang.org/x/net/proxy"
	"log/slog"
	"net"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"path"
	"syscall"
	"text/template"
	"time"
)

type TrojanConfig struct {
	Configs       []ServerConfig `json:"configs"`
	LocalPort     int            `json:"localPort"`
	Index         int            `json:"index"`
	Global        bool           `json:"global"`
	Enabled       bool           `json:"enabled"`
	IsDefault     bool           `json:"isDefault"`
	Socks5Address string         `json:"socks5_address"`
	Socks5Port    int            `json:"socks5_port"`
	HttpAddress   string         `json:"http_address"`
	HttpPort      int            `json:"http_port"`
	PacAddress    string         `json:"pac_address"`
	PacPort       int            `json:"pac_port"`
}

type ServerConfig struct {
	Server            string `json:"server"`
	ServerPort        int    `json:"server_port"`
	Password          string `json:"password"`
	TcpFastOpen       bool   `json:"tcp_fast_open"`
	Verify            bool   `json:"verify"`
	VerifyCertificate bool   `json:"verify_certificate"`
	VerifyHostname    bool   `json:"verify_hostname"`
	Type              string `json:"type"`
	Remarks           string `json:"remarks"`
}

type ManagerConfig struct {
	LocalTrojanPort             int `json:"localTrojanPort"`             // 本地trojan服务端口
	CheckRemoteServerOkInterval int `json:"checkRemoteServerOkInterval"` // 检查翻墙节点是否可用的时间间隔，单位秒
	CheckGoogleTimeout          int `json:"checkGoogleTimeout"`          // 访问google的超时时间
	CheckGoogleCount            int `json:"checkGoogleCount"`            // 访问google次数，超过这个次数则判定翻墙节点不可用
	CheckGoogleInterval         int `json:"checkGoogleInterval"`         // 访问google的间隔时间，单位秒
}

var managerConfig ManagerConfig

const trojanGuiConfigFilename = "conf/trojan_gui_config.json"
const ConfigTplFilename = "conf/config_tpl.txt"

var httpSock5Cli *http.Client
var trojanCancel context.CancelFunc
var trojanCmd *exec.Cmd

func main() {
	pwdPath, _ := os.Getwd()
	// 配置log
	slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil)))

	// 解析配置文件
	if configBytes, err := os.ReadFile(path.Join(pwdPath, "conf/config.json")); err != nil {
		slog.Error("读取配置文件conf/config.json失败", err)
		return
	} else if err = json.Unmarshal(configBytes, &managerConfig); err != nil {
		slog.Error("解析配置文件conf/config.json失败", err)
		return
	}

	// config.json存在则启动
	if configBytes, err := os.ReadFile(path.Join(pwdPath, "trojan/config.json")); err == nil {
		var config = make(map[string]any)
		json.Unmarshal(configBytes, &config)
		config["local_port"] = managerConfig.LocalTrojanPort
		configBytes, _ = json.Marshal(&config)
		trojanCancel = startTrojan(pwdPath, string(configBytes), 365*24*3600)
		time.Sleep(time.Second)
	}

	sigs := make(chan os.Signal, 1)
	signal.Notify(sigs, os.Interrupt, os.Kill, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGUSR2)
	go func() {
		fmt.Printf("主进程退出信号值:%v\n", <-sigs)
		if trojanCmd != nil {
			trojanCmd.Process.Kill()
			slog.Info("kill trojan process!!!")
		}
		if trojanCancel != nil {
			trojanCancel()
		}
		os.Exit(1)
	}()

	var checkCount = 1
	for {
		if !checkGoogle(managerConfig.LocalTrojanPort, checkCount) {
			if trojanCancel != nil {
				trojanCancel()
				time.Sleep(1 * time.Second)
			}

			configStr := getTrojanConfig(pwdPath)
			if configStr != "" {
				checkCount = managerConfig.CheckGoogleCount
				trojanCancel = startTrojan(pwdPath, configStr, 365*24*3600)
			} else {
				continue
			}
		}

		time.Sleep(time.Duration(managerConfig.CheckRemoteServerOkInterval) * time.Second)
	}
}

func getTrojanConfig(pwdPath string) string {
	// 2. 读取trojan_gui_config.json配置
	var trojanConfigFilePath = path.Join(pwdPath, trojanGuiConfigFilename)
	configBytes, err := os.ReadFile(trojanConfigFilePath)
	if err != nil {
		slog.Error("读取"+trojanGuiConfigFilename+"文件内容失败:", err)
		return ""
	}

	var trojanConfig TrojanConfig
	if err := json.Unmarshal(configBytes, &trojanConfig); err != nil {
		slog.Error("解析"+trojanGuiConfigFilename+"文件内容报错:", err)
		return ""
	}

	// 3. 读取模板文件
	var configTplFilePath = path.Join(pwdPath, ConfigTplFilename)
	configTplBytes, err := os.ReadFile(configTplFilePath)
	if err != nil {
		slog.Error("读取"+ConfigTplFilename+"文件内容报错:", err)
		return ""
	}

	// 4. 验证可用的
	for _, item := range trojanConfig.Configs {
		configStr, err := tplParse(string(configTplBytes), map[string]any{
			"LocalPort":  managerConfig.LocalTrojanPort,
			"Server":     item.Server,
			"ServerPort": item.ServerPort,
			"Password":   item.Password,
		})
		if err != nil {
			slog.Error("解析文件内容报错:", err)
			return ""
		}

		trojanCancel = startTrojan(pwdPath, configStr, 60)
		time.Sleep(time.Second)
		if checkGoogle(managerConfig.LocalTrojanPort, 1) {
			slog.Info(fmt.Sprintf("%v,%v,%v => connect success!", item.Server, item.ServerPort, item.Password))
			trojanCancel()
			return configStr
		} else {
			slog.Info(fmt.Sprintf("%v,%v,%v => connect failed!", item.Server, item.ServerPort, item.Password))
		}
		trojanCancel()

		time.Sleep(time.Second)
	}

	return ""
}

// 启动trojan
func startTrojan(pwdPath, configStr string, timeout int) context.CancelFunc {
	ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
	go func(ctx context.Context) {
		defer cancel()
		if err := os.WriteFile(path.Join(pwdPath, "trojan/config.json"), []byte(configStr), 0755); err != nil {
			slog.Error("修改配置文件trojan/config.json失败", err)
		}
		cmdStr := fmt.Sprintf("cd %s && ./trojan/sbin/trojan -c ./trojan/config.json", pwdPath)
		trojanCmd = exec.CommandContext(ctx, "/bin/bash", "-c", cmdStr)
		if err := trojanCmd.Run(); err != nil {
		}
	}(ctx)
	return cancel
}

// 检查是否能访问谷歌
func checkGoogle(port, checkCount int) bool {
	if httpSock5Cli == nil {
		dialer, err := proxy.SOCKS5("tcp", fmt.Sprintf("127.0.0.1:%d", port), nil, proxy.Direct)
		if err != nil {
			slog.Error("checkGoogle dialer err->", err)
			return false
		}

		clt := http.Client{
			Timeout: time.Duration(managerConfig.CheckGoogleTimeout) * time.Second,
			Transport: &http.Transport{
				TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
				//Dial:            dialer.Dial,
				DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
					return dialer.Dial(network, addr)
				},
			},
		}

		httpSock5Cli = &clt
	}

	var isOk = false
	for i := 0; i < checkCount; i++ {
		resp, err := httpSock5Cli.Get("https://www.google.com")
		if err != nil {
			//slog.Error("checkGoogle resp err->", err)
			isOk = false
			if checkCount != 1 {
				time.Sleep(time.Duration(managerConfig.CheckGoogleInterval) * time.Second)
			}
			continue
		}
		resp.Body.Close()

		if resp.StatusCode == http.StatusOK {
			return true
		} else {
			isOk = false
			if checkCount != 1 {
				time.Sleep(time.Duration(managerConfig.CheckGoogleInterval) * time.Second)
			}
		}
	}

	return isOk
}

// TplParse 解析模板，https://www.cnblogs.com/f-ck-need-u/p/10053124.html
func tplParse(tmpl string, values interface{}) (string, error) {
	instance := template.New("tpl")
	if tt, err := instance.Parse(tmpl); err != nil {
		return "", err
	} else {
		var tpl bytes.Buffer
		if err = tt.Execute(&tpl, &values); err != nil {
			return "", err
		}

		return tpl.String(), nil
	}
}
